/*******************************************************************************
 * Copyright (c) 2004, 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    IBM Corporation - Initial API and implementation
 *******************************************************************************/
package org.eclipse.stp.policy_builder.tcpmonitor.views;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.stp.policy_builder.tcpmonitor.source.impl.TCPManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.wst.internet.monitor.ui.internal.ContextIds;
import org.eclipse.wst.internet.monitor.ui.internal.Messages;
import org.eclipse.wst.internet.monitor.ui.internal.MonitorUIPlugin;
import org.eclipse.wst.internet.monitor.ui.internal.provisional.ContentViewer;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;


public class PolicyViewer extends ContentViewer {

	public PolicyViewer() {
		// TODO Auto-generated constructor stub
	}

	/** (non-Javadoc)
	 * @see ContentViewer#setEditable(boolean)
	 */
	public void setEditable(boolean editable) {
		messageText.setEditable(editable);
	}

	/** (non-Javadoc)
	 * @see ContentViewer#getContent()
	 */
	protected Composite viewerComp;
	protected GridLayout layout;
	protected Text messageText;
	protected Label messageLabel;

	protected boolean xmlTagMissing = false;
	protected boolean setEncoding = false;
	protected boolean missingEncoding = false;
	protected String originalEncoding;

	protected byte[] content;
    protected Document outDoc;
	/** (non-Javadoc)
	 * @see ContentViewer#setContent(byte[])
	 */
	public void setContent(byte[] b) {
		content = b;
		String out = "";	
		if (b != null) {
			b = MonitorUIPlugin.unzip(b);
			out = MonitorUIPlugin.parse(b);
		}
		
		String lineSeparator = System.getProperty("line.separator");
		int ls = lineSeparator.length();
		if (out.length() > ls) {
			while (out.substring(0, ls).indexOf(lineSeparator) >= 0)
			out = out.substring(ls, out.length());
		}
		
		String out_temp = out.toLowerCase();
		xmlTagMissing = !out_temp.startsWith("<?xml");
		
		if (out.length() > 0) {
			String finalMsg = null;
			try {
				byte[] b1 = createDocument(out);
				finalMsg = new String(b1).trim();
			} catch (Exception e) {
				// case: error parsing
				messageText.setVisible(false);
				//layout.topControl = messageLabel;
				messageLabel.setVisible(true);
				messageLabel.setText(Messages.xmlViewInvalid);
				return;
			}
			if (xmlTagMissing && finalMsg.toLowerCase().startsWith("<?xml version=\"1.0\" encoding=\"utf-8\"?>")) {
				int x = finalMsg.indexOf("\n") + 1;
				String Msg = finalMsg.substring(x);
				finalMsg = Msg;
				
				messageText.setText(finalMsg);
			} else if (setEncoding) {
				// change back to original encoding
				int begin = finalMsg.toLowerCase().indexOf("utf-8"); // location of opening "
				if (begin >= 0) {
					int last = begin + 5;  // location of closing "
					String first_half = finalMsg.substring(0,begin);
					String second_half = finalMsg.substring(last);
					finalMsg = first_half + originalEncoding + second_half;
				}
				
				messageText.setText(finalMsg);
			} else if (missingEncoding) {
				// remove encoding completely
				int begin = finalMsg.toLowerCase().indexOf("encoding=\"utf-8\""); //location of opening "
				int last = begin + 16;  //location of closing "
				String first_half = finalMsg.substring(0,begin);
				String second_half = finalMsg.substring(last);
				finalMsg = first_half + second_half;
				
				messageText.setText(finalMsg);	
			} else
				messageText.setText(finalMsg);
		} else
			messageText.setText(out);
		
		messageLabel.setVisible(false);
		//layout.topControl = messageText;
		messageText.setVisible(true);
	}

	/**
	 * @see ContentViewer#getContent()
	 */
	public byte[] getContent() {
		return content;
	}

	/** (non-Javadoc)
	 * @see ContentViewer#init(Composite)
	 */
	public void init(Composite parent) {
		viewerComp = new Composite(parent, SWT.VERTICAL);
		layout = new GridLayout();
		layout.numColumns = 2;
		viewerComp.setLayout(layout);
		GridData data = new GridData(GridData.FILL_BOTH);
		data.horizontalSpan = 2;
		data.grabExcessHorizontalSpace = true;
		viewerComp.setLayoutData(data);
		
		data = new GridData();
		data.horizontalAlignment = GridData.FILL;
		data.verticalAlignment = GridData.FILL ;
		data.grabExcessHorizontalSpace=true;
		data.grabExcessVerticalSpace=true;
		data.horizontalSpan = 2;
		data.verticalSpan = 5 ;
		messageText = new Text(viewerComp, SWT.BORDER | SWT.MULTI | SWT.READ_ONLY | SWT.H_SCROLL | SWT.V_SCROLL);
		Display display = viewerComp.getDisplay();
		messageText.setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));
		messageText.setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
		messageText.setFont(JFaceResources.getTextFont());
		messageText.setVisible(true);
		messageText.setLayoutData(data);
		PlatformUI.getWorkbench().getHelpSystem().setHelp(messageText, ContextIds.VIEW_RESPONSE);
		
		data = new GridData();
		data.horizontalAlignment = GridData.FILL;
		data.grabExcessHorizontalSpace=true;
		data.horizontalSpan = 2;
		messageLabel = new Label(viewerComp, SWT.NONE);
		messageLabel.setVisible(false);
		messageLabel.setLayoutData(data);
		
		data = new GridData();
		Label l = new Label(viewerComp,SWT.NONE);
	    l.setText("XPath expr :");
	    l.setLayoutData(data);
	    
	    data = new GridData();
	    data.grabExcessHorizontalSpace=true;
	    data.horizontalAlignment = GridData.FILL ;
	    final Text exprText = new Text(viewerComp,SWT.BORDER | SWT.MULTI);
	    exprText.setText("<Enter XPath>");
	    exprText.setLayoutData(data);
	    exprText.addKeyListener(new KeyAdapter(){
	    	public void keyReleased(KeyEvent event){
	    		if(event.keyCode == SWT.CR ){
	    			String result = setXpathResult(exprText);
	    			cacheMessage(result);
	    			exprText.setText("");
	    		}
	    	}
	    });
	    
	    data = new GridData();
		data.verticalSpan = 1 ;
		data.horizontalSpan = 2;
		final Button link1 = new Button(viewerComp,SWT.PUSH);
		link1.setBounds(15, 15, 120, 28);
		link1.setText("Extract Policy");
	    link1.setLayoutData(data);
	    link1.addSelectionListener(new SelectionAdapter(){

			public void widgetSelected(SelectionEvent event) {
				// TODO Auto-generated method stub
				String result = setXpathResult(exprText);
				cacheMessage(result);
			}
	    	
	    });
		//layout.topControl = messageText;
	}

	/* (non-Javadoc)
	 * @#createDocument(String)
	 */
	public String setXpathResult(Text control){
		String exprStr = control.getText();
		String xpathResult ="";
		if(exprStr!=null && !exprStr.trim().equals(""))
			xpathResult = executeXPath(exprStr);
		
		if(xpathResult!=null && !xpathResult.trim().equals(""))
			messageText.setText(xpathResult);
		
		return xpathResult;
	}
	
	private static void cacheMessage(String message){
		if(!"".equals(message))
			TCPManager.getManager().setMessage(message);
	}
	
	protected byte[] createDocument(String str) throws IOException {
		try {	
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			try {
				factory.setAttribute("http://apache.org/xml/features/allow-java-encodings", new Boolean(true));
				factory.setAttribute("http://apache.org/xml/features/continue-after-fatal-error", new Boolean(true));
			} catch (Exception e) {
				// ignore
			}
			factory.setNamespaceAware(true);
			DocumentBuilder parser = factory.newDocumentBuilder();
			
			if (xmlTagMissing) {
				str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + str;
			} else {
				String str_temp = str.toLowerCase();
				
				// if encoding present, then save original encoding and change to UTF-8
				int ind = str_temp.indexOf("encoding=");
				if (ind >= 0) {
					String temp1 = str.substring(ind);
					int beginIndex = temp1.indexOf("\"") + 1;
					String temp2 = temp1.substring(beginIndex);
					int endIndex = temp2.indexOf("\"");
					originalEncoding = temp2.substring(0, endIndex);
					if (!"utf-8".equals(originalEncoding))
						setEncoding = true;
				} else if (ind < 0) { //if no encoding at all,then no changes to be made
					setEncoding = false;
					missingEncoding = true;
				}
			}
			byte[] parseArray = str.getBytes();
			Document document = parser.parse(new InputSource(new ByteArrayInputStream(parseArray)));
			byte[] result = getContents(document);
			Document outDocument = parser.parse(new InputSource(new ByteArrayInputStream(result)));
			this.outDoc = outDocument;
			return result;
		} catch (Exception e) {
			throw new IOException("Invalid XML");
		}
	}

	protected byte[] getContents(Node document) throws IOException {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		Result result = new StreamResult(out);
		Source source = new DOMSource(document);
		try {
			TransformerFactory tf = TransformerFactory.newInstance();
			try {
				tf.setAttribute("indent-number", new Integer(2));
			} catch (Exception e) {
				e.printStackTrace();
				// ignore - fails on JDK 1.4
			}
			Transformer transformer = tf.newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
			transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
			transformer.transform(source, result);
		} catch (TransformerConfigurationException e) {
			throw (IOException) (new IOException().initCause(e));
		} catch (TransformerException e) {
			throw (IOException) (new IOException().initCause(e));
		}
		return out.toByteArray();
	}

	/** (non-Javadoc)
	 * @see PolicyViewer#executeXPath()
	 */
	public String executeXPath(String exprStr){
		String output="";
		if(outDoc!=null){
			XPathFactory factory = XPathFactory.newInstance();
	
		//  Use the XPathFactory to create a new XPath object
			XPath xpath = factory.newXPath();
	
		//  Compile an XPath string into an XPathExpression
			XPathExpression expression;
				try {
					
					NamespaceContext nsContext = new XMLNamespaceContext(new URIMapper(outDoc));
				    xpath.setNamespaceContext(nsContext);
					//expression = xpath.compile("//soapenv:Header");
					expression = xpath.compile(exprStr);
				    
				// 4. Evaluate the XPath expression on an input document
						Object result = expression.evaluate(outDoc,XPathConstants.NODESET);
						NodeList r = (NodeList) result ;
						
						for(int i=0;i<r.getLength();i++){
							Node temp = r.item(i);
							byte[] b = getContents(temp);
							String tempStr = new String(b).trim();
							System.out.println(tempStr);
							output = output + tempStr + "\n";
						}
						
						return output;
						
				}
				catch (XPathExpressionException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} 
				catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} 
		}
		return output;
	}
		/** (non-Javadoc)
	 * @see ContentViewer#dispose()
	 */
	public void dispose() {
		viewerComp.dispose();
		viewerComp = null;
		content = null;
		outDoc = null;
		//executeXPath();
	}

}
